#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>

#include "ZDebug.h"
#include "ZDoubleQueue.h"

const int32_t ZDebug::LOG_SIZE = 1024;
/*********************| 内部工具 |*********************/
struct Msg {
    char*       data;
    size_t      len;
    Msg(const char* data, size_t len) {
        this->data = (char*)malloc(len);
        memcpy(this->data, data, len);
        this->len = len;
    }
    ~Msg() {
        free(this->data);
        this->data = nullptr;
        this->len = 0;
    }
};

struct ZDebug::Info {
    bool                        running;
    bool                        inited;
    uint16_t                    port;
    std::string                 ip;
    int                         fd;
    sockaddr_in*                servaddr;
    std::thread*                tid;
    std::mutex                  mtx;
    std::condition_variable     cv;
    ZDoubleQueue<struct Msg*>   msgQueue;
};

static void addMsg(ZDebug::Info* info, const char* data, size_t dataLen) {
    {
        std::lock_guard<std::mutex> lock(info->mtx);
        info->msgQueue.push(new Msg(data, dataLen));
    }
    info->cv.notify_one();
}

static void cleanMsg(ZDebug::Info* info) {
    std::lock_guard<std::mutex> lock(info->mtx);
    info->msgQueue.foreach([](struct Msg* e) {
        delete e;
    });
    info->msgQueue.clean();
    info->msgQueue.swap();
    info->msgQueue.foreach([](struct Msg* e) {
        delete e;
    });
    info->msgQueue.clean();
}

static void threadFunc(ZDebug::Info* info) {
    fprintf(stderr, "ZDebug thread %p is running. [addr = %s:%u]\n", info->tid, info->ip.c_str(), info->port);
    while (info->running) {
        {
            std::unique_lock<std::mutex> lock(info->mtx);
            if (info->msgQueue.size(true) > 0) {
                info->msgQueue.swap();
            } else {
                info->cv.wait(lock);
                continue;
            }
        }
        // 发送所有的数据
        info->msgQueue.foreach([info](struct Msg* msg) {
            sendto(info->fd, msg->data, msg->len, 0, (struct sockaddr *)info->servaddr, sizeof(struct sockaddr_in));
            delete msg;
        });
        info->msgQueue.clean();
    }
    fprintf(stderr, "ZDebug thread %p exit. [addr = %s:%u]\n", info->tid, info->ip.c_str(), info->port);
}

/*********************| 外部接口 |*********************/

ZDebug::ZDebug() : _info(nullptr) {
}

ZDebug::~ZDebug() {
    if (_info) {
        // 停止线程
        _info->running = false;
        _info->cv.notify_one();
        _info->tid->join();
        delete _info->tid;
        // 清空消息列表
        cleanMsg(_info);
        // 关闭sokect
        close(_info->fd);
        free(_info->servaddr);
        delete _info;
        _info = nullptr;
    }
}

int ZDebug::init(const char* ip, uint16_t port) {
    if (_info) {
        return 0;
    }
    if (!ip) {
        fprintf(stderr, "Invalid IP address in ZDebug::init\n");
        return -1;
    }
    // 打开socket
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        fprintf(stderr, "Open socket failed in ZDebug::init\n");
        return -1;
    }

    struct sockaddr_in* servaddr = (struct sockaddr_in*)malloc(sizeof(struct sockaddr_in));
    memset(servaddr, 0, sizeof(sockaddr_in));
    servaddr->sin_family = AF_INET;
    servaddr->sin_addr.s_addr = inet_addr(ip);
    servaddr->sin_port = htons(port);

    // 创建结构体
    _info = new Info();
    _info->ip = ip;
    _info->port = port;
    _info->fd = sockfd;
    _info->servaddr = servaddr;
    _info->running = true;
    // 启动线程
    _info->tid = new std::thread(threadFunc, _info);
    _info->inited = true;

    return 0;
}

int ZDebug::log(const char* format, ...) {
    if (!_info || !format) {
        return -1;
    }
    char buf[ZDebug::LOG_SIZE];
    memset(buf, 0, ZDebug::LOG_SIZE);

    va_list args;
    va_start(args, format);
    int nstr = vsnprintf(buf, ZDebug::LOG_SIZE, format, args);
    va_end(args);

    if (nstr > 0) {
        addMsg(_info, buf, (size_t)nstr);
        return 0;
    }

    return -1;
}

const char* ZDebug::getIP() const {
    if (_info) {
        return _info->ip.c_str();
    }
    return nullptr;
}

uint16_t ZDebug::getPort() const {
    if (_info) {
        return _info->port;
    }
    return 0;
}

int ZDebug::getFD() const {
    if (_info) {
        return _info->fd;
    }
    return -1;
}
